#pragma once 

#include "point.h"
#include "material.h"

namespace PS
{

template<int Dim>
class Particle
{
protected:
    Point<Dim> _position;
    Vector<Dim> _velocity;
    scalar _velocity_angular = 0.0;
    scalar _mass;
    scalar _volume;
    std::shared_ptr<Material> _material;

public:
    Particle();
    virtual ~Particle() = default;
    Particle(const Particle<Dim>& other);
    Particle(Particle<Dim>&& other) noexcept;
    Particle<Dim>& operator=(const Particle<Dim>& other);
    Particle<Dim>& operator=(Particle<Dim>&& other) noexcept;

public:
    template<typename ... T>
    void set_position(T ... val);

    template<typename ... T>
    void set_velocity(T ... val);

    std::shared_ptr<Material> material() const;

    void reinit_material(std::shared_ptr<Material> mat);

    Point<Dim>& position();

    Vector<Dim>& velocity();

    scalar& velocity_angular();

    scalar velocity_angular() const;

    scalar mass() const;

    const Point<Dim>& position() const;

    const Vector<Dim>& velocity() const;

    virtual void update_volume() = 0;

    virtual void update_mass() = 0;

    virtual void update_inertia() = 0;
};


class Circle : public Particle<2>
{
private:
    scalar _radius = 1.0;
    scalar _inertia = 0.0;

public:
    scalar radius() const;

    scalar inertia() const;

    void set_radius(scalar r);

    virtual void update_volume() override;

    virtual void update_mass() override;

    virtual void update_inertia() override;
};

} // namespace PS


namespace PS
{

template<int Dim>
Particle<Dim>::Particle() : _position(), _velocity()
{
}

template<int Dim>
Particle<Dim>::Particle(const Particle<Dim>& other) : _position(other._position), _velocity(other._velocity)
{
}

template<int Dim>
Particle<Dim>::Particle(Particle<Dim>&& other) noexcept : _position(std::move(other._position)), _velocity(std::move(other._velocity))
{
    other._position.clear();
    other._velocity.clear();
}

template<int Dim>
Particle<Dim>& Particle<Dim>::operator=(const Particle<Dim>& other)
{
    _position = other._position;
    _velocity = other._velocity;

    return *this;
}

template<int Dim>
Particle<Dim>& Particle<Dim>::operator=(Particle<Dim>&& other) noexcept
{
    _position = std::move(other._position);
    _velocity = std::move(other._velocity);
    other._position.clear();
    other._velocity.clear();

    return *this;
}


template<int Dim>
void 
Particle<Dim>::reinit_material(std::shared_ptr<Material> mat)
{
    _material = mat;
}

template<int Dim>
std::shared_ptr<Material> 
Particle<Dim>::material() const
{
    return _material;
}

template<int Dim>
Point<Dim>& 
Particle<Dim>::position()
{
    return _position;
}


template<int Dim>
const Point<Dim>& 
Particle<Dim>::position() const
{
    return _position;
}

template<int Dim>
Vector<Dim>& 
Particle<Dim>::velocity()
{
    return _velocity;
}


template<int Dim>
const Vector<Dim>& 
Particle<Dim>::velocity() const
{
    return _velocity;
}

template<int Dim>
scalar& 
Particle<Dim>::velocity_angular()
{
    return _velocity_angular;
}
    
template<int Dim>
scalar 
Particle<Dim>::velocity_angular() const
{
    return _velocity_angular;
}

template<int Dim>
scalar
Particle<Dim>::mass() const
{
    return _mass;
}

template<int Dim>
template<typename ... T>
void 
Particle<Dim>::set_position(T ... val)
{
    _position.set(val ...);
}

template<int Dim>
template<typename ... T>
void 
Particle<Dim>::set_velocity(T ... val)
{
    _velocity.set(val ...);
}

inline scalar 
Circle::radius() const
{
    return _radius;
}

inline void 
Circle::set_radius(scalar r)
{
    _radius = r;
}

inline void 
Circle::update_volume()
{
    _volume = 3.1415926 * _radius * _radius;
}

inline void 
Circle::update_mass()
{
    _mass = _material->density() * _volume;
}

inline void 
Circle::update_inertia()
{
    _inertia = 0.5 * _mass * _radius * _radius;
}

inline scalar 
Circle::inertia() const
{
    return _inertia;
}

} // namespace PS